package monsmon.term;

import java.awt.event.*;
import java.awt.*;
import javax.swing.*;
import java.awt.image.*;
import java.util.concurrent.ConcurrentLinkedQueue;

import static java.awt.event.KeyEvent.*;

/*
 * WinTerm: Implementation of Terminal for windowing systems
 *	fully supports all Termial functionality
 *
 * 
 */
public class WinTerm extends JComponent implements Terminal{

	private Surface __draw, __show;
	private char __carr[] = new char[1];
	
	private JFrame __win = null;
	private Font __font = new Font("Monospaced", Font.PLAIN, 14);
	
	//Blink timer.  Chars blink every 500 secs
	private Timer t = new Timer(500, new ActionListener(){
		public void actionPerformed(ActionEvent e){
			blk = !blk;
			repaint();
		}	
	});
	
	private int __x_sz, __y_sz, __font_x, __font_y;
	private int __x_chars, __y_chars;

	private ConcurrentLinkedQueue<Key> __kq =
		new ConcurrentLinkedQueue<Key>();
	private Object mutex = new Object();
	private volatile boolean blk = false;
	
	public void init(){
		try{
			SwingUtilities.invokeLater(new Runnable(){
				public void run(){ __init(); }
			});
			synchronized(mutex){
				mutex.wait();
			}
		}catch(Exception ex){
			ex.printStackTrace();
		}
	}

	public void __init(){

		__win = new JFrame("");

		__win.addKeyListener(new __KillListener());
		__win.addKeyListener(new __key_capture());

		__win.setExtendedState(JFrame.MAXIMIZED_BOTH);
		__win.setSize(__win.getGraphicsConfiguration().
				getBounds().getSize());
		__win.getContentPane().setLayout(new BorderLayout());
		__win.getContentPane().add(this, BorderLayout.CENTER);	
		__win.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		__win.getContentPane().setBackground(Color.BLACK);
		__win.setVisible(true);
		
		Dimension d = __win.getSize();
		__x_sz = (int) d.getWidth();
		__y_sz = (int) d.getHeight();

		Image bf = __win.createImage(5,5);
		Graphics gf = bf.getGraphics();
		FontMetrics fm = gf.getFontMetrics(__font);
		__font_x = fm.charWidth(' ');
		__font_y = fm.getHeight();	
		
		__x_chars = __x_sz / __font_x;
		__y_chars = __y_sz / __font_y;

		__draw = new Surface(__x_chars, __y_chars);
		__show = new Surface(__x_chars, __y_chars);

		t.start();
		synchronized(mutex){
			mutex.notifyAll();
		}
	}

	public void paintComponent(Graphics g){
		g.setColor(Color.BLACK);
		g.fillRect(0, 0, __x_sz, __y_sz);
		synchronized(__show){
			__draw_chars(g, __show);
		}
		g.dispose();
	}

	private void __draw_chars(Graphics g, Surface cshow){
		if(cshow == null) return;
		Color bg;
		g.setColor(Color.WHITE);
		g.setFont(__font);
		Tchar tc = null;
		for(int x = 0; x < cshow.get_w(); x++){
			for(int y = 0; y < cshow.get_h(); y++){
				tc = cshow.get(x, y);
				__carr[0] = tc.c;
				//Do the blink
				if(tc.blink && blk){
					if(!Color.BLACK.equals(tc.bg) 
							&& tc.bg != null){
						g.setColor(tc.bg);
						g.fillRect(x * __font_x, 
							y * __font_y + 3,
							__font_x + 1,
							__font_y);
					}
					if(tc.altc == ' ') continue;
					g.setColor(tc.fg);
					__carr[0] = tc.altc;
					g.drawChars(__carr, 0, 1, x * __font_x
						+ 1, (y + 1) * __font_y);
					continue;
				}
				bg = tc.bg;
				if(!Color.BLACK.equals(bg) && bg != null){
					g.setColor(bg);
					g.fillRect(x * __font_x, y * __font_y +
						3, __font_x + 1, __font_y);
				}

				if(tc.c == ' ') continue;
				g.setColor(tc.fg);
				g.drawChars(__carr, 0, 1, x * __font_x + 1,
					(y+1) * __font_y);
			}
		}
	}

	public void update(){
		synchronized(__show){
			__draw.__fast_blit(__show);
		}
		repaint();
	}

	public void clear(){
		__draw.clear();
	}

	public void destroy(){
		__win.dispose();
		__draw = null;
		__show = null;
		__win = null;
		__font = null;
		__kq = null;
		t.stop();
		t = null;
	}

	public int get_x_len(){
		return __x_chars;
	}

	public int get_y_len(){
		return __y_chars;
	}

	public void draw(int x, int y, char c){
		draw(x, y, c, Color.WHITE, Color.BLACK, false, ' ', 
				Color.WHITE);
	}

	public void draw(int x, int y, char c, Color fg){
		draw(x, y, c, fg, Color.BLACK, false, ' ', Color.WHITE);
	}

	public void draw(int x, int y, char c, Color fg, Color bg){
		draw(x, y, c, fg, bg, false, ' ', Color.WHITE);
	}

	public void draw(int x, int y, char c, Color fg, Color bg, 
			boolean blink){
		draw(x, y, c, fg, bg, blink, ' ', Color.WHITE);
	}

	public void draw(int x, int y, char c, Color fg, boolean blink, 
			char altc){
		draw(x, y, c, fg, Color.BLACK, blink, altc, Color.WHITE);
	}

	public void draw(int x, int y, Tchar tc){
		draw(x, y, tc.c, tc.fg, tc.bg, tc.blink, tc.altc, tc.altfg);
	}

	public void draw(int x, int y, char c, Color fg, Color bg,
			boolean blink, char altc, Color altfg){
		__draw.draw(x, y, c, fg, bg, blink, altc, altfg);
	}
	
	public Surface get_curr_surface(){
		return __draw;
	}

	public void copy_buff(){
		__draw.blit(__show, null, 0, 0);
	}

	
	/*
	 * KeyListener to catch CTRL-SHIFT-C events for the magic kill
	 * 	switch
	 */
	private class __KillListener extends KeyAdapter{

		private boolean __shift, __crtl, __c;

		@Override
		public void keyPressed(KeyEvent e){
			switch(e.getKeyCode()){
				case KeyEvent.VK_CONTROL:
					__crtl = true;
					break;
				case KeyEvent.VK_SHIFT:
					__shift = true;
					break;
				case KeyEvent.VK_C:
					__c = true;
					break;
				}
			if(__shift && __crtl && __c) System.exit(1);
		}
	
		@Override
		public void keyReleased(KeyEvent e){
			switch(e.getKeyCode()){
				case KeyEvent.VK_CONTROL:
					__crtl = false;
					break;
				case KeyEvent.VK_SHIFT:
					__shift = false;
					break;
				case KeyEvent.VK_C:
					__c = false;
					break;
			}
		}
	}
	
	//Prints the draw buffer to stdout
	//Debug only
	public void __dump_buffer(){
		__draw.__dump_buffer();
	}

	/*
	 *	Get the top key event
	 */
	public Key poll_event(){
		return __kq.poll();
	}

	public Key peek_event(){
		return __kq.peek();
	}

	public void clear_event(){
		__kq.clear();
	}
	
	/*
	 *	New style key interface.
	 *		keys get sent to __kq
	 */
	private class __key_capture extends KeyAdapter{

		@Override
		public void keyTyped(KeyEvent e){
			char ch = e.getKeyChar();
			boolean alt = e.isAltDown(),
			ctrl = e.isControlDown(),
			meta = e.isMetaDown(),
			shift = e.isShiftDown();
			__kq.add(new Key(ch, alt, ctrl, meta, shift));
		}



		//Catch non-print keys not caught by keyTyped
		@Override
		public void keyPressed(KeyEvent e){
			int kc = 0;
			switch(e.getKeyCode()){
				case VK_LEFT: kc = Key. K_LEFT; break;
				case VK_RIGHT: kc = Key. K_RIGHT; break;
				case VK_DOWN: kc = Key. K_DOWN; break;
				case VK_UP: kc = Key. K_UP; break;
				case VK_ESCAPE: kc = Key. K_ESCAPE; break;
				case VK_HOME: kc = Key. K_HOME; break;
				case VK_INSERT: kc = Key. K_INSERT; break;
				case VK_DELETE: kc = Key. K_DELETE; break;
				case VK_END: kc = Key. K_END; break;
				case VK_TAB: kc = Key. K_TAB; break;

				case VK_F1: kc = Key. K_F1; break;
				case VK_F2: kc = Key. K_F3; break;
				case VK_F3: kc = Key. K_F3; break;
				case VK_F4: kc = Key. K_F4; break;
				case VK_F5: kc = Key. K_F5; break;
				case VK_F6: kc = Key. K_F6; break;
				case VK_F7: kc = Key. K_F7; break;
				case VK_F8: kc = Key. K_F8; break;
				case VK_F9: kc = Key. K_F9; break;
				case VK_F10: kc = Key. K_F10; break;
				case VK_F11: kc = Key. K_F11; break;
				case VK_F12: kc = Key. K_F12; break;
				
				case VK_ENTER: kc = '\n'; break;
			}
			if(kc == 0) return;
			__kq.add(new Key((char) kc, e.isAltDown(), 
				e.isControlDown(), e.isMetaDown(), 
				e.isShiftDown()));
		}
	}
}
